#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "printers.h"
#include "errors.h"

static int indentAmount=3;

static void _printXPath10(const struct Node* node, enum PrintFormat format,
                          unsigned int indent);
static void _printXPath21(const struct Node* node, enum PrintFormat format,
                          unsigned int indent, struct MemBlockInfo* mem);
static void _printNode(enum PrintFormat format,int level, 
                            const char* name);
static void _printValueNode(enum PrintFormat format, int level, 
                            const char* name, const char* value);
static char* _concatValueNodes(struct Node* start, int names, 
							struct MemBlockInfo* mem);

static void _printStringLiteral(enum PrintFormat format, int level,
                                const char* name, const char* value,
                                const char quote);

void printXPath(struct Node* node, enum PrintFormat format, 
                enum XPathVersion version, struct MemBlockInfo* mem)
{
    switch (version)
    {
        case XPATH_10: printXPath10(node,format); break;
        case XPATH_20: printXPath20(node,format); break;
        case XPATH_21: printXPath21(node,format,mem); break;
        default:       printXPath10(node,format); break;
    }
}

void printNode(struct Node* node,enum PrintFormat format)
{
	printXPath10(node,format);
}

void printXPath10(struct Node* node, enum PrintFormat format)
{
	assert(node);

    node=nodeGetFirstSibling(node);

    while (node)
    {
        _printXPath10(node,format,0);
        node=node->right;
    }
}


void printXPath20(struct Node* node, enum PrintFormat format)
{
    printf("ERROR: XPath 2.0 print method not implemented.\n");
}

void printXPath21(struct Node* node, enum PrintFormat format,
					struct MemBlockInfo* mem)
{
    assert(node);

    node=nodeGetFirstSibling(node);

    while (node)
    {
        _printXPath21(node,format,0,mem);
        node=node->right;
    }
}

static void _printXPath10(const struct Node* node, enum PrintFormat format,
                          unsigned int indent)
{
	assert(node);

    if (node->type==NODE_STRING)
    {
        if (node->extra.quotes)
            _printStringLiteral(format,indent,node->name,(char*)node->data.p,
                                node->extra.quotes);
        else
            _printValueNode(format,indent,node->name,(char*)node->data.p);
    }
    else
        _printNode(format,indent,node->name);

	if (node->children>0)
	{
		node=node->first;

		while (node)
		{
			_printXPath10(node,format,indent+1);
			node=node->right;
		}
	}
}

static char* _concatValueNodes(struct Node* start, int names, 
                               struct MemBlockInfo* mem)
{
    char* buffer=NULL;
    char* string=NULL;
    int buflen=1;   /* \0 */
    int stringlen=0;
    struct Node* node=start;

    /* Iterate over all nodes */
    while (node)
    {
        string=NULL;

        /* Determine whether we need to concatenate the value of
           the node, or the name of the node */
        if (node->type==NODE_STRING)
            string=(char*)node->data.p;
        else
        if (names)
            string=(char*)node->name;

        /* If there should be something to contenate, then ... */
        if (string)
        {
            stringlen=strlen(string);

            /* If there indeed is something to work with, then ...*/
            if (stringlen)
            {
                /* Grow the buffer */
                buffer=realloc(buffer,buflen+stringlen);

                if (!buffer)
                    memerror(buflen+1,__func__,__FILE__,__LINE__,mem);

                /* Copy the data. We use memcpy instead of strcpy
                   because we already know the length of the string */
                memcpy(buffer+buflen-1,string,stringlen);

                /* Store the new buffer length */
                buflen+=stringlen;

                /* Make sure the buffer is zero-terminated */
                buffer[buflen-1]='\0';
            }
        }

        /* Next please */
        node=node->right;
    }

    /* Return the new string. Might be NULL */
    return buffer;
}

static void _printNode(enum PrintFormat format, int level, const char* name)
{
    switch(format)
    {
        case PRINT_TEXT:    if (level)
                                printf("%*s%s\n",level*indentAmount," ",name);
                            else
                                printf("%s\n",name);
                            break;
        case PRINT_TABLE:   printf("%d,%s\n",level,name); break;
        case PRINT_XML:     printf("%*s<%s>\n",level*indentAmount," ",name); 
                            break;
    }
}

static void _printValueNode(enum PrintFormat format, int level,
                            const char* name, const char* value)
{
    switch (format)
    {
        case PRINT_TEXT:    if (level)
                                printf("%*s%s %s\n",level*indentAmount," ",
                                       name,value);
                            else
                                printf("%s %s\n",name,value);
                            break;
        case PRINT_TABLE:   printf("%d,%s,%s\n",level,name,value); break;
        case PRINT_XML:     printf("%*s<%s value=\"%s\">\n",
                                   level*indentAmount," ",name,value);
                            break;
    }
}


static void _printStringLiteral(enum PrintFormat format, int level,
                                const char* name, const char* value, 
                                const char quote)
{
    switch (format)
    {
        case PRINT_TEXT:    if (level)
                                printf("%*s%s %c%s%c\n",level*indentAmount," ",
                                       name,quote,value,quote);
                            else
                                printf("%s %c%s%c\n",name,quote,value,quote);
                            break;
        case PRINT_TABLE:   printf("%d,%s,%c%s%c\n",level,name,quote,value,
                                   quote);
                            break;
        case PRINT_XML:     printf("%*s<%s value=%c%s%c>\n",
                                   level*indentAmount," ",name,quote,value,
                                   quote);
                            break;
    }
}

static void _printXPath21(const struct Node* node, enum PrintFormat format,
                          unsigned int indent, struct MemBlockInfo* mem)
{
    char* value=NULL;

	assert(node);

    if (node->type==NODE_STRING)
    {
        if (isRule(node->parent,NodeTest) &&
           (isToken(node->parent->first,PROCESSING_INSTRUCTION)))
            _printStringLiteral(format,indent+1,"StringLiteral",
                            (char*)node->data.p,node->extra.quotes);
        else
            _printStringLiteral(format,indent,"StringLiteral",
                                (char*)node->data.p,node->extra.quotes);
    }
    else
    if (node->type==NODE_RULE)
    {
        switch (node->data.nt)
        {
            case AbbreviatedStep:
                                if (node->stepKind==STEP_REVERSE)
                                    _printNode(format,indent,
                                               "AbbrevReverseStep");
                                else
                                if (node->stepKind==STEP_CURRENT)
                                    _printValueNode(format,indent,
                                                    "ContextItemExpr",
                                                    node->first->name);

                                return;

            case AbsoluteLocationPath: if (!isRule(node->first,
                                           AbbreviatedAbsoluteLocationPath))
                                       {
                                           _printValueNode(format,indent,
                                                           "Slash","/");
                                       }

                                       indent--;
                                       break;

            case AndExpr:
            case OrExpr:
            case MultiplicativeExpr:
            case AdditiveExpr:  _printValueNode(format,indent,node->name,
                                                node->first->right->name);
                                break;
            case Arguments: indent--; break;
            case Argument:  indent--; break;
            case AxisSpecifier:
                                /* AbbrevForwardStep: ('@')? NodeTest */
                                if (isToken(node->first,AT_SIGN))
                                {
                                    _printValueNode(format,indent,
                                                    "AbbrevForwardStep","@");
                                    return;
                                }

                                if (node->stepKind==STEP_FORWARD)
                                    _printValueNode(format,indent,
                                                    "ForwardAxis",
                                                    node->first->first->name);
                                else
                                if (node->stepKind==STEP_REVERSE)
                                    _printValueNode(format,indent,
                                                    "ReverseAxis",
                                                    node->first->first->name);
                                else
                                    _printNode(format,indent,"<UNKNOWN>");
                                return;

            case Expr:       if (isRule(node->parent,XPath)  ||
                                 isToken(node->left,LEFT_PAREN))
                                _printNode(format,indent,"Expr"); 
                             break;

            case FilterExpr: if (!isRule(node->parent,FilterExpr))
                                 _printNode(format,indent,"StepExpr"); 
                             else
                                 indent--;
                             break;

            case FunctionCall: _printNode(format,indent,"FunctionCall");

                            /* Print function name */
                            _printXPath21(node->first,format,indent+1,mem);
                            
                            if (node->children>1)
                            {
                                /* Arguments */
                                node=node->first->right;

                                while (node)
                                {
                                    _printXPath21(node,format,indent,mem);
                                    node=node->right;
                                }
                            }

                            return;

            case NodeTest:
                             /* AbbrevForwardStep: NodeTest */
                            if (isRule(node->parent,Step) && (!node->left))
                            {
                                _printNode(format,indent,"AbbrevForwardStep");
                                indent++;
                            }
                            else
                             /* AbbrevForwardStep: '@' NodeTest */
                            if (isRule(node->left,AxisSpecifier) &&
                                isToken(node->left->first,AT_SIGN))
                                    indent++;

                            _printNode(format,indent,node->name); 
                                                    
                            break;

            case QName:     
                            /* Concatenate the values of all child nodes.
                               Use the node name if it doesn't have a value */
                            value=_concatValueNodes(node->first,1,mem);

                            if (isRule(node->parent,FunctionName))
                                _printValueNode(format,indent,
                                                "FunctionQName",value); 
                            else 
                                _printValueNode(format,indent,
                                                "QName",value); 

                            /* Release the concatenated value */
                            free(value);

                            return;

            case PathExpr:  if (node->extra.isPath)
                                _printNode(format,indent,"PathExpr"); 
                            else
                                indent--;
                            break;

            case PredicateExpr: _printNode(format,indent,"Expr");
                                indent--;
                                break;

            case Predicates: _printNode(format,indent,"PredicateList");
                             break;

            case PrimaryExpr: if (isToken(node->first,LEFT_PAREN))
                                  _printNode(format,
                                             indent,
                                             "ParenthesizedExpr");
                              else
                                  indent--;
                              break;

            case EqualityExpr:
            case RelationalExpr: 
                            _printValueNode(format,indent,"ComparisonExpr",
                                            node->first->right->name); 
                            break;

            case Step:      _printNode(format,indent,"StepExpr"); 
                            break;

            case UnaryExpr: if (!isRule(node->parent,UnaryExpr))
                                _printNode(format,indent,"UnaryExpr"); 
                            else
                                indent--;
                            break;

            case UnionExpr:  _printValueNode(format,indent,node->name,
                                                node->first->right->name);
                                break;
            case VariableReference: _printNode(format,indent,"VarName"); 
                                    break;

            /* ignored */
            case AbbreviatedAbsoluteLocationPath:
            case AbbreviatedRelativeLocationPath:
            case FunctionName:
            case LocationPath:
            case RelativeLocationPath:
                            /* Decrease indent for this node */
                            indent--;
                            break;
            default:    
                        _printNode(format,indent,node->name);
        }
    }
    else
    if (node->type==NODE_TOKEN)
    {
        switch (node->data.t)
        {
            case ANY:           _printValueNode(format,indent,
                                                "Wildcard",node->name);
                                break;


            case MINUS:         if (isRule(node->parent,UnaryExpr))
                                    _printValueNode(format,indent,
                                                    "Minus",node->name);
                                break;

            case NUMBER:        /* XPath 2.1 has a DecimalLiteral type */
                                if (strstr(node->name,"."))
                                    _printValueNode(format,indent,
                                                "DecimalLiteral",node->name);
                                else
                                    _printValueNode(format,indent,
                                                "IntegerLiteral",node->name);
                                break;

            case SLASH_SLASH:   _printValueNode(format,indent,
                                                "SlashSlash",node->name);
                                break;

            case TEXT:          _printNode(format,indent,"TextTest");
                                break;

            case NODE:          _printNode(format,indent,"AnyKindTest");
                                break;

            case COMMENT:       _printNode(format,indent,"CommentTest");
                                break;

            case PROCESSING_INSTRUCTION: _printNode(format,indent,"PITest");
                                break;


            default:            /* skip */ ;
        }
    }
    else
        _printNode(format,indent,node->name);

	if (node->children>0)
	{
		node=node->first;

		while (node)
		{
			_printXPath21(node,format,indent+1,mem);
			node=node->right;
		}
	}
}


